package dst.ass2.ejb.session;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.logging.Logger;

import javax.ejb.AsyncResult;
import javax.ejb.Asynchronous;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;

import dst.ass1.jpa.model.IMembership;
import dst.ass1.jpa.model.ITask;
import dst.ass1.jpa.model.ITaskWorker;
import dst.ass1.jpa.model.IUser;
import dst.ass1.jpa.model.IWorkPlatform;
import dst.ass2.ejb.dto.AuditLogDTO;
import dst.ass2.ejb.dto.BillDTO;
import dst.ass2.ejb.dto.BillDTO.BillPerTask;
import dst.ass2.ejb.management.PriceManagementBean;
import dst.ass2.ejb.management.interfaces.IPriceManagementBean;
import dst.ass2.ejb.model.IAuditLog;
import dst.ass2.ejb.session.exception.AssignmentException;
import dst.ass2.ejb.session.interfaces.IGeneralManagementBean;
import dst.ass2.ejb.util.EJBUtils;

/**
 * Assignment Share not state, invoked independently of each other, invokable by client directly. 
 */

@Remote(IGeneralManagementBean.class)
@Stateless
public class GeneralManagementBean implements IGeneralManagementBean {
	
	private final IPriceManagementBean priceManagementBean;
	
	private final static Logger LOGGER = Logger.getLogger(GeneralManagementBean.class.getName());

    @PersistenceContext
    private EntityManager entityManager;

    public GeneralManagementBean() throws NamingException {
        priceManagementBean = EJBUtils.lookup(new InitialContext(), PriceManagementBean.class);  //lookup the PriceManagementBean
        LOGGER.info("GeneralManagementBean lookup for PriceManagementBean: "+priceManagementBean);
    }

	@Override
	public void addPrice(Integer nrOfHistoricalTasks, BigDecimal price) {		//addNewPriceStep
		 priceManagementBean.setPrice(nrOfHistoricalTasks, price);				//call in PriceManagementBean
		 LOGGER.info("Added Price in GeneralManagementBean");
	}


	/** 2.1.b Retrieve the bill of a user*/
	@Override
	@Asynchronous
	public Future<BillDTO> getBillForUser(String username) throws Exception {  
		BillDTO bill = new BillDTO();					//Bill DataTransferObject
        bill.setBills(new LinkedList<BillDTO.BillPerTask>());	//List of BillPerTask
        bill.setUsername(username);						//Name of the User
        bill.setTotalPrice(new BigDecimal(0));			//TotalPrice will be sum up in loop

        //check if User exists
        @SuppressWarnings("unused")
		IUser user = null;
        try {
            user = entityManager.createQuery("SELECT u FROM User u WHERE u.username = :username", IUser.class)
                    .setParameter("username", username).getSingleResult();
        } catch (NoResultException e) {
            throw new AssignmentException("Couldn't find user", e);
        }
        
        
        //1) retrieve all unpaid tasks for the given user
        List<ITask> notPaidTasks = entityManager.createQuery("SELECT t FROM Task t WHERE isPaid = false AND t.user.username = :name", ITask.class)
                .setParameter("name", username).getResultList();   //need the List and not only the size

        //2.1. query for the number of already paid tasks
        int paidTasksValue = entityManager.createQuery("SELECT t FROM Task t WHERE isPaid = true AND t.user.username = :name", ITask.class)
                .setParameter("name", username).getResultList().size();   //need only the size
          
        //2) for each unpaid task do
        for (ITask task : notPaidTasks) {			//calculate the Bill for each Task that is not paid
        	calcBillPerTask(bill,task,paidTasksValue++);  
        	//paid tasks (p) should steadily increase (assignment note)
        }

        entityManager.flush();

        return new AsyncResult<BillDTO>(bill);
	}
	
	
	private void calcBillPerTask(BillDTO bill,ITask task,int countPaidTasks) throws AssignmentException {
        
		List<ITaskWorker> taskWorkers = task.getTaskProcessing().getTaskWorkers();  //get all TaskWorkers from the Task
		IWorkPlatform workPlatform=null;
		
		
        if (taskWorkers==null || taskWorkers.isEmpty()) {				//should NOT be empty
        	LOGGER.info("Task not assigned to any TaskWorker");
            throw new AssignmentException("Task is not assigned to any TaskWorker");
        }
        else{
        	workPlatform = taskWorkers.get(0).getTaskForce().getWorkPlatform();  //get the WorkPlatform 
         
	        //get all Memberships from a User with a particular Workplatform ID
	        List<IMembership> memberships = entityManager
	                .createQuery("SELECT m FROM User u JOIN u.memberships m JOIN m.workPlatform w " +
	                             "WHERE u.username = :name AND w.id = :workplatformid", IMembership.class)
	                .setParameter("name", bill.getUsername())
	                .setParameter("workplatformid", workPlatform.getId())
	                .getResultList();
	
	        
	        //do some billing math
	        BigDecimal discount;
	        
	        if(memberships!=null && memberships.get(0)!=null && memberships.get(0).getDiscount()!=null)   //if available set discount
	        	discount= new BigDecimal(memberships.get(0).getDiscount());
	        else
	        	discount= new BigDecimal(0);		//otherwise set 0
	        
	        LOGGER.info("Calculated Discount for Task : "+task.getId()+" is "+discount);
	        
	        //calculate (1-d)
	        BigDecimal paymentFraction = new BigDecimal(1).subtract(discount);		//1-discount is the price you pay
	        
	        //2.2 determine the setup costs cs from the PriceManagementBean with the paramater p
	        BigDecimal cs = priceManagementBean.getPrice(countPaidTasks);  //calculated Price with included Discount
	        
	        //calculate processingTime
	        Integer processingTime=0;
	        
	        if (task.getProcessingTime() == null || task.getProcessingTime() == 0) {  //if not setted in task
	        	Date end=null;
	        	
	        	if(task.getTaskProcessing().getEnd() == null)
	        		end=new Date();
	        	else
	        		end=task.getTaskProcessing().getEnd();	

	            Date start = task.getTaskProcessing().getStart();
	            
	            processingTime = (int)(end.getTime() - start.getTime()) / 1000;   //processing time in seconds
	        }
	        else{		//if it is setted than take it
	        	processingTime= task.getProcessingTime();  //processing time in seconds
	        }
	        
	        BigDecimal processingTimeMinutes = new BigDecimal(processingTime/60);   //get Minutes from ProcessingTime Seconds
	        
	        //2.3. determine the processing costs cp based on the processing time and the platforms costsPerWorkUnit
	        BigDecimal cp = workPlatform.getCostsPerWorkUnit().multiply(processingTimeMinutes); //calculated ExecutionCosts per Minute with included Discount
	
	        //2.4. determine the total tasks costs ct=(cs+cp) * (1-d)
	        BigDecimal ct=(cs.add(cp)).multiply(paymentFraction);
	        
	        
	        //Setting all information in the Data Transfer Object
	        BillPerTask billPerTask = bill.new BillPerTask();
	        billPerTask.setTaskId(task.getId());
	        billPerTask.setSetupCosts(cs.setScale(2, RoundingMode.HALF_UP));
	        billPerTask.setProcessingCosts(cp.setScale(2, RoundingMode.HALF_UP));
	        billPerTask.setNumberOfWorkers(task.getTaskProcessing().getTaskWorkers().size());
	        billPerTask.setTaskCosts(ct.setScale(2, RoundingMode.HALF_UP));
	
	        //Adding/Summing up the BillDTO information
	        bill.getBills().add(billPerTask);  //Add the Bill to the Task
	        bill.setTotalPrice(bill.getTotalPrice().add(billPerTask.getTaskCosts()));  //add new BillPrice to Total Bill
	
	        //2.5. setting the task to Paid=true
	        task.setPaid(true);
	        
	        LOGGER.info("Bill for Task is setted to Paid: TaskId: "+task.getId());
	        
	        //2.6. persit task in the database
	        entityManager.persist(task);
        }
    }
	

	@Override
	public List<AuditLogDTO> getAuditLogs() {
		List<AuditLogDTO> auditLogs = new ArrayList<AuditLogDTO>(); 

        List<IAuditLog> logs = entityManager.createQuery("SELECT a FROM AuditLog a", IAuditLog.class).getResultList();
        for (IAuditLog log : logs) {
        	auditLogs.add(new AuditLogDTO(log));   //change to Data transfer object (DTO)
        }

        LOGGER.info("Received AutditLogs in GeneralManagementBean");
        
        return auditLogs;
	}

	@Override
	public void clearPriceCache() {
		priceManagementBean.clearCache();
		LOGGER.info("ClearPriceCache in GeneralManagementBean");
	}
}
